package edu.vt.cbil.core;

import affymetrix.calvin.exception.UnsignedOutOfLimitsException;
import affymetrix.fusion.cdf.FusionCDFData;
import affymetrix.fusion.cdf.FusionCDFProbeGroupInformation;
import affymetrix.fusion.cdf.FusionCDFProbeInformation;
import affymetrix.fusion.cdf.FusionCDFProbeSetInformation;
import affymetrix.fusion.cel.FusionCELData;
import edu.vt.cbil.dataset.*;
import edu.vt.cbil.util.BioToolbox;
import edu.vt.cbil.util.ConfigObject;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.*;
import java.util.logging.Logger;

/**
 * The type Bio data reader.
 *
 * @author Justin
 * @version 1.0
 * @created 09 -11月-2014 10:50:34
 */

public class BioDataReader {

    private final boolean Minimize_Memory_Usage = true;//节约内存使用，不存储一些调试用的String数据
    private AnnotationDataSet p_AnnotationData;
    private ConfigObject p_config;
    private String curSampleId;
    private BioSample p_sample;
    private Logger m_log;
    private List<Map<Long, RawIntensityProbeSet>> rawData;
    private BioCollection p_Collection;

    /**
     * Instantiates a new Bio data reader.
     *
     * @param c  the ConfigObject
     * @param d  the BioCollection
     * @param pa the AnnotationDataSet
     */
    public BioDataReader(ConfigObject c, BioCollection d, AnnotationDataSet pa) {
        m_log = Logger.getLogger("edu.vt.cbil.bacom2");

        p_config = c;
        p_Collection = d;
        p_AnnotationData = pa;

        rawData = new ArrayList<Map<Long, RawIntensityProbeSet>>();
        //U 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 U X Y MT
        for (int i = 0; i < 27; i++) {//第0，第23无用；
            rawData.add(new HashMap<Long, RawIntensityProbeSet>(BioToolbox.ChromosomeLength[i]));
        }

    }

//    public void finalize() throws Throwable {
//        super.finalize();
//        rawData = null;
//    }

    /**
     * Reading bio data.
     *
     * @param NormalFileName the normal file name
     * @param TumorFileName  the tumor file name
     * @param OnlySNP        the only sNP
     */
    public boolean readingBioData(String NormalFileName, String TumorFileName, boolean OnlySNP) {

        FusionCELData celNormal;
        FusionCELData celTumor;
        FusionCDFData cdf;

        try {
            celNormal = new FusionCELData();
            celNormal.setFileName(NormalFileName);
            if (!celNormal.read()) {
                throw new Exception("Failed to read the Normal Sample file.");
            }

            celTumor = new FusionCELData();
            celTumor.setFileName(TumorFileName);
            if (!celTumor.read()) {
                throw new Exception("Failed to read the Tumor Sample file.");
            }

            cdf = new FusionCDFData();
            cdf.setFileName(p_config.getCDFFile());
            if (!cdf.read()) {
                throw new Exception("Failed to read the CDF file.");
            }

            //Only for SNP 6.0
            if (celNormal.getChipType().equals(cdf.getChipType()) && celTumor.getChipType().equals(cdf.getChipType())
                    && cdf.getChipType().equals("GenomeWideSNP_6")) {
                // System.out.println(cdf.getChipType());

                int nsets = cdf.getHeader().getNumProbeSets();

                ProbeSetType ptype;
                // Probe Set
                for (int iset = 0; iset < nsets; iset++) {

                    String probeSetName = cdf.getProbeSetName(iset); // get the probeset name

                    ptype = checkProbeSetType(probeSetName);

                    if (ptype.equals(ProbeSetType.UNUSED))
                        continue;

                    AnnotationProbeSet aps = p_AnnotationData.get(probeSetName);
                    if (aps == null)
                        continue;

                    if (!p_Collection.isInterestedChromonsome(aps.Chr_id)) continue;

                    FusionCDFProbeSetInformation set = new FusionCDFProbeSetInformation();
                    cdf.getProbeSetInformation(iset, set);
                    int ngroups = set.getNumGroups();

                    //new one
                    RawIntensityProbeSet rawProbe = new RawIntensityProbeSet();
                    rawProbe.Chr_id = aps.Chr_id;
                    rawProbe.Loci = aps.Pos;
                    rawProbe.Start = aps.Start;
                    rawProbe.Is_CNProbe = ptype.equals(ProbeSetType.CN);
                    rawProbe.Is_SNPProbe = ptype.equals(ProbeSetType.SNP);

                    if (!Minimize_Memory_Usage) {
                        rawProbe.ProbeSetID = probeSetName;
                        rawProbe.Cytoband = aps.Cytoband;
                    }

                    if (OnlySNP && rawProbe.Is_CNProbe) {
                        continue;
                    }

                    // Probe Group
                    for (int igroup = 0; igroup < ngroups; igroup++) {

                        FusionCDFProbeGroupInformation group = new FusionCDFProbeGroupInformation();
                        set.getGroup(igroup, group);
                        int ncells = group.getNumCells();

                        //find Allele A Group
                        boolean isA = false;
                        if (ptype.equals(ProbeSetType.SNP)) {
                            isA = isAllele_A(group, aps);
                        }

                        // Probe Cell
                        for (int icell = 0; icell < ncells; icell++) {
                            FusionCDFProbeInformation probe = new FusionCDFProbeInformation();
                            group.getCell(icell, probe);

                            addIntensityToRawProbeSet(celNormal, celTumor, rawProbe, isA, probe);
                        }

                    }

//					if ((rawProbe.Normal.Allele_A.size() == 0) && (rawProbe.Normal.CNProbe.size()==0)){
//						 System.out.println(rawProbe.Loci + " CN and Allele_A.size() == 0");
//					}

                    // add to map, 考虑重合的情况
                    Map<Long, RawIntensityProbeSet> rmap = rawData.get(rawProbe.Chr_id);
                    RawIntensityProbeSet alr = rmap.get(rawProbe.Loci);
                    if (alr == null) {
                        rmap.put(rawProbe.Loci, rawProbe);
                    } else {
                        if (!Minimize_Memory_Usage) {
                            alr.ProbeSetID.concat(rawProbe.ProbeSetID);
                        }

                        alr.Is_CNProbe |= rawProbe.Is_CNProbe;
                        alr.Is_SNPProbe |= rawProbe.Is_SNPProbe;

                        if (rawProbe.Is_CNProbe) {
                            alr.Normal.CNProbe.addAll(rawProbe.Normal.CNProbe);
                            alr.Tumor.CNProbe.addAll(rawProbe.Tumor.CNProbe);
                        } else {
                            alr.Normal.Allele_A.addAll(rawProbe.Normal.Allele_A);
                            alr.Normal.Allele_B.addAll(rawProbe.Normal.Allele_B);

                            alr.Tumor.Allele_A.addAll(rawProbe.Tumor.Allele_A);
                            alr.Tumor.Allele_B.addAll(rawProbe.Tumor.Allele_B);
                        }
                        //System.out.println("???" + alr.ProbeSetID + alr.OtherProbeSetID);
                    }

                }

            } else {
                throw new Exception("The chip type of CEL file and CDF file is different!");
            }
        } catch (Exception e) {
            System.err.println(e.getClass().getName() + " Error : " + e.getMessage());
            //System.exit(0);
            return false;
        }

        displayReadingResult();
        return true;
    }

    private void displayReadingResult() {
        int sum = 0;
        for (int i = 1; i < 27; i++) {
            int count = rawData.get(i).size();
            if (count > 0) {
                sum += count;
                m_log.info(String.format("Chr %02d;\t probes count = %d\t", i, rawData.get(i).size()));
            }
        }
        m_log.info(String.format("\t sum of probes of all chromosomes = %s\n", sum));
    }

    private void addIntensityToRawProbeSet(FusionCELData celNormal, FusionCELData celTumor, RawIntensityProbeSet rawProbe, boolean isA,
                                           FusionCDFProbeInformation probe) throws IOException, UnsignedOutOfLimitsException {

        int x = probe.getX();
        int y = probe.getY();

        if (!celNormal.isOutlier(x, y)) {

            if (rawProbe.Is_SNPProbe) {
                if (isA) {
                    rawProbe.Normal.Allele_A.add(celNormal.getIntensity(x, y));
                } else {
                    rawProbe.Normal.Allele_B.add(celNormal.getIntensity(x, y));
                }
            } else {
                rawProbe.Normal.CNProbe.add(celNormal.getIntensity(x, y));
            }
        }

        if (!celTumor.isOutlier(x, y)) {

            if (rawProbe.Is_SNPProbe) {
                if (isA) {
                    rawProbe.Tumor.Allele_A.add(celTumor.getIntensity(x, y));
                } else {
                    rawProbe.Tumor.Allele_B.add(celTumor.getIntensity(x, y));
                }
            } else {
                rawProbe.Tumor.CNProbe.add(celTumor.getIntensity(x, y));
            }

        }
    }

    /**
     * Determine whether a probe group is Allele_A
     *
     * @param group  probe group
     * @param annops the Annotation of a probe
     * @return
     */
    private boolean isAllele_A(FusionCDFProbeGroupInformation group, AnnotationProbeSet annops) {
        int groupid = group.getGroupIndex();
        char groupname = group.getName().trim().toUpperCase().charAt(0);

        if ((groupid == 0) && (groupname == annops.Allele_A)) // 两个条件只用考虑一个就行了，后一条件更强一些
        {
            return true;
        } else if ((groupid == 1) && (groupname == annops.Allele_B)) {
            return false;
        } else {
            System.out.println("对Allele_A B的理解 有问题？");
            return false;
        }
    }

    /**
     * @param probeSetName
     */
    private ProbeSetType checkProbeSetType(String probeSetName) {
        if (probeSetName.startsWith("SNP")) {
            return ProbeSetType.SNP;
        } else if (probeSetName.startsWith("CN")) {
            return ProbeSetType.CN;
        } else {
            return ProbeSetType.UNUSED;
        }
    }

    /**
     * Filter raw data [(1-lower)*mean, (1+upper)*mean]
     *
     * @param lower the lower limit
     * @param upper the upper limit
     */
    public void filterRawData(double lower, double upper) {
        int deleteCount;
        deleteCount = 0;

        for (Map<Long, RawIntensityProbeSet> rawChromosome : rawData) {
            for (RawIntensityProbeSet rawProbe : rawChromosome.values()) {

                ArrayList<ArrayList<Float>> allArrays = rawProbe.getArrays();

                for (ArrayList<Float> a : allArrays) {
                    int len = a.size();
                    if (len > 1) {
                        a.sort(null);

                        double m = a.get(len >> 1);

                        double up = m * (1 + upper);
                        double down = Math.max(m * (1 - lower), 0);

                        Iterator<Float> it = a.iterator();
                        while (it.hasNext()) {
                            double v = it.next();
                            if ((v > up) || (v < down)) {
                                it.remove();
                                deleteCount++;
                            }
                        }
                    }
                }
            }
        }
        m_log.info("Filter removed : " + deleteCount);

    }

    /**
     * Calculate mean values.
     *
     * @param SampleId the sample id
     */
    public void calculate_SaveMeanValues(String SampleId) {
        try {
            if (SampleId.compareTo(this.curSampleId) != 0) {
                throw new Exception("Sample ID error!");
            }

            for (Map<Long, RawIntensityProbeSet> rawChromosome : rawData) {
                //for (Map.Entry<Long, RawIntensityProbeSet> probe: chromosome.entrySet()) {

                for (RawIntensityProbeSet rawProbe : rawChromosome.values()) {

                    ProbeSet newProbe = new ProbeSet(rawProbe);

                    if (rawProbe.Is_SNPProbe) {
                        newProbe.data.Normal.mean_A = BioToolbox.mean(rawProbe.Normal.Allele_A);
                        newProbe.data.Normal.mean_B = BioToolbox.mean(rawProbe.Normal.Allele_B);

                        newProbe.data.Tumor.mean_A = BioToolbox.mean(rawProbe.Tumor.Allele_A);
                        newProbe.data.Tumor.mean_B = BioToolbox.mean(rawProbe.Tumor.Allele_B);

                        newProbe.data.Normal.mean_AA = BioToolbox.squareMean(rawProbe.Normal.Allele_A);
                        newProbe.data.Normal.mean_BB = BioToolbox.squareMean(rawProbe.Normal.Allele_B);

                        newProbe.data.Tumor.mean_AA = BioToolbox.squareMean(rawProbe.Tumor.Allele_A);
                        newProbe.data.Tumor.mean_BB = BioToolbox.squareMean(rawProbe.Tumor.Allele_B);

                        newProbe.data.Normal.std_A = BioToolbox.std(newProbe.data.Normal.mean_AA, newProbe.data.Normal.mean_A);
                        newProbe.data.Normal.std_B = BioToolbox.std(newProbe.data.Normal.mean_BB, newProbe.data.Normal.mean_B);

                        newProbe.data.Tumor.std_A = BioToolbox.std(newProbe.data.Tumor.mean_AA, newProbe.data.Tumor.mean_A);
                        newProbe.data.Tumor.std_B = BioToolbox.std(newProbe.data.Tumor.mean_AA, newProbe.data.Tumor.mean_B);

                        if (rawProbe.Is_CNProbe) {//SNP和CN探针重合的情况
                            rawProbe.Normal.CNProbe.add((float) (newProbe.data.Normal.mean_A + newProbe.data.Normal.mean_B));
                            rawProbe.Tumor.CNProbe.add((float) (newProbe.data.Tumor.mean_A + newProbe.data.Tumor.mean_B));
                        }
                    }

                    if (rawProbe.Is_CNProbe) {
                        newProbe.data.Normal.mean_CN = BioToolbox.mean(rawProbe.Normal.CNProbe);

                        newProbe.data.Tumor.mean_CN = BioToolbox.mean(rawProbe.Tumor.CNProbe);

                        newProbe.data.Normal.std_CN = BioToolbox.std(rawProbe.Normal.CNProbe);

                        newProbe.data.Tumor.std_CN = BioToolbox.std(rawProbe.Tumor.CNProbe);
                    }

                    Map<Long, ProbeSet> oneChromo = p_sample.getAllProbeSets(rawProbe.Chr_id);
                    oneChromo.put(rawProbe.Loci, newProbe);
                }
            }

        } catch (Exception e) {
            System.err.println(e.getClass().getName() + " Error : " + e.getMessage());
            System.exit(0);
        }


    }

    /**
     * Clear rawData. List<Map<Long, RawIntensityProbeSet>> rawData;
     */
    public void clearData() {
        for (short i : p_Collection.getChrIdArray()) {
            if (rawData.get(i).size() > 0)
                rawData.get(i).clear();
        }
    }

    /**
     * Set biodata reader.
     *
     * @param sampleId the sample id
     */
    public void setBioDataReader(String sampleId) {
        curSampleId = sampleId;
        p_sample = p_Collection.getSample(this.curSampleId);

        clearData();
    }

    public void loadingBioDataFromImportData(String sampleId, String filename) {
        try {
            // connect sqlite database
            Class.forName("org.sqlite.JDBC");
            String ConnectionString = String.format("jdbc:sqlite:%s", filename);
            Connection c = DriverManager.getConnection(ConnectionString);

            Statement stmt = c.createStatement();
            ResultSet rs;
            rs = stmt.executeQuery("SELECT Germline_LogR.f1,Germline_LogR.chrs,Germline_LogR.pos, " +
                    String.format("Germline_LogR.%1$s as G_LogR,Germline_BAF.%1$s as G_BAF,Tumor_LogR.%1$s as T_LogR,Tumor_BAF.%1$s as T_BAF ", sampleId) +
                    "FROM Germline_LogR ,Germline_BAF ,Tumor_BAF ,Tumor_LogR " +
                    "WHERE Germline_LogR.f1 = Germline_BAF.f1 AND Germline_BAF.f1 = Tumor_BAF.f1 " +
                    "AND Tumor_BAF.f1 = Tumor_LogR.f1 AND Germline_LogR.chrs < 23 " +
                    "ORDER BY Germline_LogR.chrs,Germline_LogR.pos");

            Random r = new Random();

            while (rs.next()) {
                ProbeSet newProbe = new ProbeSet();
                newProbe.Is_SNPProbe = true;
                newProbe.Is_CNProbe = false;
                //String ProbeSetID = rs.getString("f1").trim();
                short Chr_id = rs.getShort("chrs");
                int start = rs.getInt("pos");
                long pos = BioToolbox.getGlobalLocation(Chr_id, start);
                double nLogR, nBAF, tLogR, tBAF;
                nLogR = rs.getDouble("G_LogR");
                nBAF = rs.getDouble("G_BAF");
                tLogR = rs.getDouble("T_LogR");
                tBAF = rs.getDouble("T_BAF");

                double normal_A, normal_B, tumor_A, tumor_B;
                double sum = Math.pow(2, nLogR);
                normal_B = sum * nBAF;
                normal_A = sum - normal_B;
                sum = Math.pow(2, tLogR);
                tumor_B = sum * tBAF;
                tumor_A = sum - tumor_B;

                newProbe.Chr_id = Chr_id;
                newProbe.Start = start;
                newProbe.Loci = pos;
                newProbe.data.Normal.mean_A = 1024 * normal_A;
                newProbe.data.Normal.mean_B = 1024 * normal_B;
                newProbe.data.Tumor.mean_A = 1024 * tumor_A;
                newProbe.data.Tumor.mean_B = 1024 * tumor_B;

                p_sample.getAllProbeSets(Chr_id).put(pos, newProbe);
//                for (int i = 1; i< 11;i++){
//                    ProbeSet newProbe2 = new ProbeSet();
//                    newProbe2.Is_SNPProbe = true;
//                    newProbe2.Is_CNProbe = false;
//
//                    newProbe2.Chr_id= Chr_id;
//                    newProbe2.Start = start + i;
//                    newProbe2.Loci = pos + i;
//                    newProbe2.data.Normal.mean_A = 1024*normal_A + r.nextDouble()*10-5;
//                    newProbe2.data.Normal.mean_B = 1024*normal_B + r.nextDouble()*10-5;
//                    newProbe2.data.Tumor.mean_A = 1024*tumor_A + r.nextDouble()*10-5;
//                    newProbe2.data.Tumor.mean_B = 1024*tumor_B +r.nextDouble()*10-5;
//                    p_sample.getAllProbeSets(Chr_id).put(newProbe2.Loci,newProbe2);
//                }
            }
            //System.out.println(ProbeSets.size());
            rs.close();
            stmt.close();
            c.close();

        } catch (Exception e) {
            System.err.println(e.getClass().getName() + ": " + e.getMessage());
            System.exit(0);
        }
    }
}